/*
* Copyright (c) 2021 Talkweb Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "service_http.h"

static void service_sysdep_mutex_deinit(service_http_ctx_t *http_ctx) {
    http_ctx->sysdep->service_sysdep_mutex_deinit(&http_ctx->all_mutex.data_mutex);
    http_ctx->sysdep->service_sysdep_mutex_deinit(&http_ctx->all_mutex.send_mutex);
    http_ctx->sysdep->service_sysdep_mutex_deinit(&http_ctx->all_mutex.recv_mutex);
}

static void _service_http_exec_inc(service_http_ctx_t *http_ctx)
{
    http_ctx->sysdep->service_sysdep_mutex_lock(http_ctx->all_mutex.data_mutex);
    http_ctx->service_exec_count++;
    http_ctx->sysdep->service_sysdep_mutex_unlock(http_ctx->all_mutex.data_mutex);
}

static void _service_http_exec_dec(service_http_ctx_t *http_ctx)
{
    http_ctx->sysdep->service_sysdep_mutex_lock(http_ctx->all_mutex.data_mutex);
    http_ctx->service_exec_count--;
    http_ctx->sysdep->service_sysdep_mutex_unlock(http_ctx->all_mutex.data_mutex);
}

static int32_t _service_http_sysdep_return(int32_t sysdep_code, int32_t service_code)
{
    if (sysdep_code >= (RET_PORT_BASE - 0x00FF) && sysdep_code < (RET_PORT_BASE)) {
        return sysdep_code;
    } else {
        return service_code;
    }
}

static int32_t _service_http_connect(service_http_ctx_t *http_ctx)
{
    int32_t res = RET_SUCCESS;

    /* disconnect first if network is already established */
    if (http_ctx->network_ctx != NULL) {
        http_ctx->sysdep->service_sysdep_network_deinit(&http_ctx->network_ctx);
    }

    /* establish network connection */
    service_sysdep_socket_type_t socket_type = SERVICE_SYSDEP_SOCKET_TCP_CLIENT;

    if (http_ctx->host == NULL) {
        return RET_USER_INPUT_MISSING_HOST;
    }

    http_ctx->network_ctx = http_ctx->sysdep->service_sysdep_network_init();
    if (http_ctx->network_ctx == NULL) {
        return RET_SYS_DEPEND_MALLOC_FAILED;
    }

    if ((res = http_ctx->sysdep->service_sysdep_network_setopt(http_ctx->network_ctx, SERVICE_SYSDEP_NETWORK_SOCKET_TYPE,
               &socket_type)) < 0 ||
        (res = http_ctx->sysdep->service_sysdep_network_setopt(http_ctx->network_ctx, SERVICE_SYSDEP_NETWORK_HOST,
                http_ctx->host)) < 0 ||
        (res = http_ctx->sysdep->service_sysdep_network_setopt(http_ctx->network_ctx, SERVICE_SYSDEP_NETWORK_PORT,
                &http_ctx->port)) < 0 ||
        (res = http_ctx->sysdep->service_sysdep_network_setopt(http_ctx->network_ctx,
                SERVICE_SYSDEP_NETWORK_CONNECT_TIMEOUT_MS,
                &http_ctx->connect_timeout_ms)) < 0) {
        http_ctx->sysdep->service_sysdep_network_deinit(&http_ctx->network_ctx);
        return _service_http_sysdep_return(res, RET_SYS_DEPEND_NWK_INVALID_OPTION);
    }

    if (http_ctx->cred != NULL) {
        res = http_ctx->sysdep->service_sysdep_network_setopt(http_ctx->network_ctx, SERVICE_SYSDEP_NETWORK_CRED,
                http_ctx->cred);
        if (res < RET_SUCCESS) {
            http_ctx->sysdep->service_sysdep_network_deinit(&http_ctx->network_ctx);
            return _service_http_sysdep_return(res, RET_SYS_DEPEND_NWK_INVALID_CRED);
        }
    }

    res = http_ctx->sysdep->service_sysdep_network_establish(http_ctx->network_ctx);
    if (res < RET_SUCCESS) {
        http_ctx->sysdep->service_sysdep_network_deinit(&http_ctx->network_ctx);
        return _service_http_sysdep_return(res, RET_SYS_DEPEND_NWK_EST_FAILED);
    }

    return RET_SUCCESS;
}

static int32_t _service_http_send(service_http_ctx_t *http_ctx, uint8_t *buffer, uint32_t len, uint32_t timeout_ms)
{
    int32_t res = RET_SUCCESS;

    if (http_ctx->network_ctx != NULL) {
        res = http_ctx->sysdep->service_sysdep_network_send(http_ctx->network_ctx, buffer, len, timeout_ms, NULL);
        if (res < RET_SUCCESS) {
            http_ctx->sysdep->service_sysdep_network_deinit(&http_ctx->network_ctx);
            log_error("HTTP network error when sending data, disconnect\r\n");
            res = _service_http_sysdep_return(res, RET_SYS_DEPEND_NWK_SEND_ERR);
        } else if (res != len) {
            res = RET_SYS_DEPEND_NWK_WRITE_LESSDATA;
        }
    } else {
        res = RET_SYS_DEPEND_NWK_CLOSED;
    }

    return res;
}

static int32_t _service_http_send_body(service_http_ctx_t *http_ctx, uint8_t *content, uint32_t len)
{
    int32_t res = RET_SUCCESS;

    http_ctx->sysdep->service_sysdep_mutex_lock(http_ctx->all_mutex.send_mutex);
    res = _service_http_send(http_ctx, content, len, http_ctx->send_timeout_ms);
    http_ctx->sysdep->service_sysdep_mutex_unlock(http_ctx->all_mutex.send_mutex);

    return res;
}

static void _service_http_header_print(service_http_ctx_t *http_ctx, char *prefix, char *header, uint32_t header_len)
{
    char *prev = header, *pos = header;
    uint32_t line_len = 0;
    while (pos != header + header_len) {
        if (*(pos) == '\n' && *(pos - 1) == '\r') {
            line_len = (uint32_t)(pos - prev + 1);
            log_debug("%s %.*s", prefix, line_len, prev);
            prev = pos + 1;
        }
        pos++;
    }
}

static int32_t _service_http_send_header(service_http_ctx_t *http_ctx, char *method, char *path, char *host,
                                      char *header, char *content_lenstr)
{
    int32_t res = RET_SUCCESS;
    char *combine_header = NULL;
    char *combine_header_src[] = { method, path, host, header, content_lenstr};
    uint32_t combine_header_len = 0;

    res = service_sprintf(http_ctx->sysdep, &combine_header, "%s %s HTTP/1.1\r\nHost: %s\r\n%sContent-Length: %s\r\n\r\n",
                       combine_header_src, sizeof(combine_header_src) / sizeof(char *));
    if (res < RET_SUCCESS) {
        return res;
    }
    combine_header_len = (uint32_t)strlen(combine_header);

    _service_http_header_print(http_ctx, ">", combine_header, combine_header_len);

    http_ctx->sysdep->service_sysdep_mutex_lock(http_ctx->all_mutex.send_mutex);
    res = _service_http_send(http_ctx, (uint8_t *)combine_header, combine_header_len, http_ctx->send_timeout_ms);
    http_ctx->sysdep->service_sysdep_mutex_unlock(http_ctx->all_mutex.send_mutex);

    http_ctx->sysdep->service_sysdep_free(combine_header);

    return res;
}

void *service_http_init(void)
{
    service_http_ctx_t *http_ctx = NULL;
    tiot_sysdep_portfile_t *sysdep = NULL;

    sysdep = tiot_sysdep_get_global_config();
    if (sysdep == NULL) {
        return NULL;
    }

    http_ctx = sysdep->service_sysdep_malloc(sizeof(service_http_ctx_t));
    if (http_ctx == NULL) {
        return NULL;
    }
    memset(http_ctx, 0, sizeof(service_http_ctx_t));

    http_ctx->sysdep = sysdep;
    http_ctx->connect_timeout_ms = SERVICE_HTTP_DEFAULT_CONNECT_TIMEOUT_MS;
    http_ctx->send_timeout_ms = SERVICE_HTTP_DEFAULT_SEND_TIMEOUT_MS;
    http_ctx->recv_timeout_ms = SERVICE_HTTP_DEFAULT_RECV_TIMEOUT_MS;
    http_ctx->header_line_max_len = SERVICE_HTTP_DEFAULT_HEADER_LINE_MAX_LEN;
    http_ctx->body_buffer_max_len = SERVICE_HTTP_DEFAULT_BODY_MAX_LEN;
    http_ctx->deinit_timeout_ms = SERVICE_HTTP_DEFAULT_DEINIT_TIMEOUT_MS;

    http_ctx->all_mutex.data_mutex = http_ctx->sysdep->service_sysdep_mutex_init();
    http_ctx->all_mutex.send_mutex = http_ctx->sysdep->service_sysdep_mutex_init();
    http_ctx->all_mutex.recv_mutex = http_ctx->sysdep->service_sysdep_mutex_init();

    http_ctx->service_exec_enabled = 1;

    return http_ctx;
}

int32_t service_http_setopt(void *ctx, service_http_option_t option, void *data)
{
    int32_t res = RET_SUCCESS;
    service_http_ctx_t *http_ctx = (service_http_ctx_t *)ctx;

    if (http_ctx == NULL || data == NULL) {
        return RET_USER_INPUT_NULL_POINTER;
    }

    if (option >= SERVICE_HTTPOPT_MAX) {
        return RET_USER_INPUT_OUT_RANGE;
    }

    if (http_ctx->service_exec_enabled == 0) {
        return RET_USER_INPUT_EXEC_DISABLED;
    }

    _service_http_exec_inc(http_ctx);

    http_ctx->sysdep->service_sysdep_mutex_lock(http_ctx->all_mutex.data_mutex);
    switch (option) {
        case SERVICE_HTTPOPT_HOST: {
            res = service_strdup(http_ctx->sysdep, &http_ctx->host, (char *)data);
        }
        break;
        case SERVICE_HTTPOPT_PORT: {
            http_ctx->port = *(uint16_t *)data;
        }
        break;
        case SERVICE_HTTPOPT_NETWORK_CRED: {
            if (http_ctx->cred != NULL) {
                http_ctx->sysdep->service_sysdep_free(http_ctx->cred);
                http_ctx->cred = NULL;
            }
            http_ctx->cred = http_ctx->sysdep->service_sysdep_malloc(sizeof(tiot_sysdep_network_cred_t));
            if (http_ctx->cred != NULL) {
                memset(http_ctx->cred, 0, sizeof(tiot_sysdep_network_cred_t));
                memcpy(http_ctx->cred, data, sizeof(tiot_sysdep_network_cred_t));
            } else {
                res = RET_SYS_DEPEND_MALLOC_FAILED;
            }
        }
        break;
        case SERVICE_HTTPOPT_CONNECT_TIMEOUT_MS: {
            http_ctx->connect_timeout_ms = *(uint32_t *)data;
        }
        break;
        case SERVICE_HTTPOPT_SEND_TIMEOUT_MS: {
            http_ctx->send_timeout_ms = *(uint32_t *)data;
        }
        break;
        case SERVICE_HTTPOPT_RECV_TIMEOUT_MS: {
            http_ctx->recv_timeout_ms = *(uint32_t *)data;
        }
        break;
        case SERVICE_HTTPOPT_DEINIT_TIMEOUT_MS: {
            http_ctx->deinit_timeout_ms = *(uint32_t *)data;
        }
        break;
        case SERVICE_HTTPOPT_HEADER_LINE_MAX_LEN: {
            http_ctx->header_line_max_len = *(uint32_t *)data;
        }
        break;
        case SERVICE_HTTPOPT_BODY_BUFFER_MAX_LEN: {
            http_ctx->body_buffer_max_len = *(uint32_t *)data;
        }
        break;
        case SERVICE_HTTPOPT_EVENT_HANDLER: {
            http_ctx->event_ctx = (tiot_http_event_ctx_t)data;
        }
        break;
        case SERVICE_HTTPOPT_USERDATA: {
            http_ctx->service_userdata = data;
        }
        break;
        case SERVICE_HTTPOPT_RECV_HANDLER: {
            http_ctx->service_recv_ctx = (tiot_http_recv_ctx_t)data;
        }
        break;
        default: {
            res = RET_USER_INPUT_UNKNOWN_OPTION;
        }
        break;
    }
    http_ctx->sysdep->service_sysdep_mutex_unlock(http_ctx->all_mutex.data_mutex);

    _service_http_exec_dec(http_ctx);

    return res;
}

int32_t service_http_connect(void *ctx)
{
    int32_t res = RET_SUCCESS;
    service_http_ctx_t *http_ctx = (service_http_ctx_t *)ctx;

    if (http_ctx == NULL) {
        return RET_USER_INPUT_NULL_POINTER;
    }

    if (http_ctx->service_exec_enabled == 0) {
        return RET_USER_INPUT_EXEC_DISABLED;
    }

    _service_http_exec_inc(http_ctx);

    /* connect to host */
    http_ctx->sysdep->service_sysdep_mutex_lock(http_ctx->all_mutex.send_mutex);
    http_ctx->sysdep->service_sysdep_mutex_lock(http_ctx->all_mutex.recv_mutex);
    res = _service_http_connect(http_ctx);
    http_ctx->sysdep->service_sysdep_mutex_unlock(http_ctx->all_mutex.recv_mutex);
    http_ctx->sysdep->service_sysdep_mutex_unlock(http_ctx->all_mutex.send_mutex);

    _service_http_exec_dec(http_ctx);

    return res;
}

int32_t service_http_send(void *ctx, const service_http_request_t *request)
{
    int32_t res = RET_SUCCESS;
    char content_lenstr[11] = {0};
    service_http_ctx_t *http_ctx = (service_http_ctx_t *)ctx;

    if (http_ctx == NULL || request == NULL ||
        request->path == NULL || request->method == NULL) {
        return RET_USER_INPUT_NULL_POINTER;
    }

    if (http_ctx->host == NULL) {
        return RET_USER_INPUT_MISSING_HOST;
    }

    if (http_ctx->network_ctx == NULL) {
        return RET_SYS_DEPEND_NWK_CLOSED;
    }

    if (http_ctx->service_exec_enabled == 0) {
        return RET_USER_INPUT_EXEC_DISABLED;
    }

    memset(&http_ctx->session, 0, sizeof(service_http_session_t));

    _service_http_exec_inc(http_ctx);

    /* send http header */
    service_uint2str(request->content_len, content_lenstr, NULL);
    res = _service_http_send_header(http_ctx, request->method, request->path, http_ctx->host, request->header,
                                 content_lenstr);
    if (res < RET_SUCCESS) {
        _service_http_exec_dec(http_ctx);
        return res;
    } else {
        res = RET_SUCCESS;
    }

    /* send http content */
    if (request->content != NULL && request->content_len > 0) {
        res = _service_http_send_body(http_ctx, request->content, request->content_len);
        if (res < RET_SUCCESS) {
            _service_http_exec_dec(http_ctx);
            return res;
        }
    }

    _service_http_exec_dec(http_ctx);

    return res;
}

static void _service_http_recv_status_code(service_http_ctx_t *http_ctx, uint32_t status_code)
{
    tiot_http_recv_t packet;

    if (http_ctx->service_recv_ctx == NULL) {
        return;
    }

    memset(&packet, 0, sizeof(tiot_http_recv_t));
    packet.type = TIOT_HTTPRECV_STATUS_CODE;
    packet.data.status_code.code = status_code;

    http_ctx->service_recv_ctx(http_ctx, &packet, http_ctx->service_userdata);
}

static void _service_http_recv_header_pair(service_http_ctx_t *http_ctx, char *key, uint32_t key_len, char *value,
                                        uint32_t value_len)
{
    char *pair_key = NULL, *pair_value = NULL;
    tiot_http_recv_t packet;

    if (http_ctx->service_recv_ctx == NULL) {
        return;
    }

    pair_key = http_ctx->sysdep->service_sysdep_malloc(key_len + 1);
    if (pair_key == NULL) {
        return;
    }
    memset(pair_key, 0, key_len + 1);
    memcpy(pair_key, key, key_len);

    pair_value = http_ctx->sysdep->service_sysdep_malloc(value_len + 1);
    if (pair_value == NULL) {
        http_ctx->sysdep->service_sysdep_free(pair_key);
        return;
    }
    memset(pair_value, 0, value_len + 1);
    memcpy(pair_value, value, value_len);

    packet.type = TIOT_HTTPRECV_HEADER;
    packet.data.header.key = pair_key;
    packet.data.header.value = pair_value;

    http_ctx->service_recv_ctx(http_ctx, &packet, http_ctx->service_userdata);

    http_ctx->sysdep->service_sysdep_free(pair_key);
    http_ctx->sysdep->service_sysdep_free(pair_value);
}

static int32_t _service_http_recv(service_http_ctx_t *http_ctx, uint8_t *buffer, uint32_t len, uint32_t timeout_ms)
{
    int32_t res = RET_SUCCESS;

    if (http_ctx->network_ctx != NULL) {
        res = http_ctx->sysdep->service_sysdep_network_recv(http_ctx->network_ctx, buffer, len, timeout_ms, NULL);
        if (res < RET_SUCCESS) {
            http_ctx->sysdep->service_sysdep_network_deinit(&http_ctx->network_ctx);
            log_error("HTTP network error when receving data, disconnect\r\n");
            res = _service_http_sysdep_return(res, RET_SYS_DEPEND_NWK_RECV_ERR);
        }
    } else {
        res = RET_SYS_DEPEND_NWK_CLOSED;
    }

    return res;
}

static int32_t _service_http_recv_header(service_http_ctx_t *http_ctx, uint32_t *body_total_len)
{
    int32_t res = RET_SUCCESS;
    char *line = NULL;
    uint32_t idx = 0, line_max_len = http_ctx->header_line_max_len;
    uint64_t timenow_ms = 0;

    line = http_ctx->sysdep->service_sysdep_malloc(line_max_len);
    if (line == NULL) {
        return RET_SYS_DEPEND_MALLOC_FAILED;
    }
    memset(line, 0, line_max_len);

    timenow_ms = http_ctx->sysdep->service_sysdep_time();
    for (idx = 0; idx < line_max_len;) {
        if (timenow_ms > http_ctx->sysdep->service_sysdep_time()) {
            timenow_ms = http_ctx->sysdep->service_sysdep_time();
        }
        if (http_ctx->sysdep->service_sysdep_time() - timenow_ms >= http_ctx->recv_timeout_ms) {
            res =  RET_HTTP_HEADER_INVALID;
            break;
        }
        if (idx + 2 > line_max_len) {
            res = RET_HTTP_HEADER_BUFFER_TOO_SHORT;
            break;
        }
        /* read http header, "\r\n" in the end */
        if ((res = _service_http_recv(http_ctx, (uint8_t *)&line[idx], 1,
                                   http_ctx->recv_timeout_ms)) < RET_SUCCESS) {
            break;
        }
        idx++;
        if (res == 0 || (line[idx - 1] != '\r')) {
            continue;
        }
        if ((res = _service_http_recv(http_ctx, (uint8_t *)&line[idx], 1,
                                   http_ctx->recv_timeout_ms)) < RET_SUCCESS) {
            break;
        }
        idx++;
        if (res == 0 || (line[idx - 1] != '\n')) {
            continue;
        }

        log_debug("--< %.*s", idx, line);
        /* next line should be http response body */
        if (idx == 2) {
            break;
        }
        /* status code */
        if ((idx > (strlen("HTTP/1.1 ") + 3)) && (memcmp(line, "HTTP/1.1 ", strlen("HTTP/1.1 "))) == 0) {
            uint32_t status_code = 0, code_idx = 0;
            for (code_idx = strlen("HTTP/1.1 "); code_idx < idx; code_idx++) {
                if (line[code_idx] < '0' || line[code_idx] > '9') {
                    break;
                }
            }
            res = service_str2uint(&line[strlen("HTTP/1.1 ")], (code_idx - strlen("HTTP/1.1 ")), &status_code);
            if (res < RET_SUCCESS) {
                res = RET_HTTP_STATUS_LINE_INVALID;
                break;
            }
            _service_http_recv_status_code(http_ctx, status_code);
        }
        /* header */
        {
            uint32_t deli_idx = 0;
            int len = 0;
            for (deli_idx = 0; deli_idx < idx; deli_idx++) {
                if (line[deli_idx] == ':' && line[deli_idx + 1] == ' ') {
                    //if ((deli_idx + 2 == strlen("bodyLength: ")) && (memcmp(line, "bodyLength: ", deli_idx + 2) == 0)) {
                    if ((deli_idx + 2 == strlen("Content-Length: ")) && (memcmp(line, "Content-Length: ", deli_idx + 2) == 0)) {
                        service_str2uint(&line[deli_idx + 2], (uint32_t)(idx - deli_idx - 4), &len);
                        *body_total_len = len;
                    }
                    _service_http_recv_header_pair(http_ctx, line, deli_idx, &line[deli_idx + 2], (uint32_t)(idx - deli_idx - 4));
                }
            }
        }
        idx = 0;
        memset(line, 0, line_max_len);
    }

    http_ctx->sysdep->service_sysdep_free(line);

    return res;
}

static int _service_htoi(unsigned char *s)
{
    int i;
    int n = 0;
    if (s[0] == '0' && (s[1]=='x' || s[1]=='X')) { //判断是否有前导0x或者0X
        i = 2;
    } 
    else {
        i = 0;
    }
    for (; (s[i] >= '0' && s[i] <= '9') || (s[i] >= 'a' && s[i] <= 'z') || (s[i] >='A' && s[i] <= 'Z');++i){
        if (tolower(s[i]) > '9') {
            n = 16 * n + (10 + tolower(s[i]) - 'a'); 
        } 
       else 
        { 
            n = 16 * n + (tolower(s[i]) - '0'); 
        } 
    } 
    return n; 
} 

static int32_t _service_get_chunked_count(service_http_ctx_t *http_ctx)
{
    int32_t res = 0;
    uint8_t line[12] = {0};
    int32_t idx = 0;

    http_ctx->sysdep->service_sysdep_mutex_lock(http_ctx->all_mutex.recv_mutex);
    for(;;){
        /* "0\r\n" in the end */
        if ((res = _service_http_recv(http_ctx, (uint8_t *)&line[idx], 1,
                                   http_ctx->recv_timeout_ms)) < RET_SUCCESS) {
            break;
        }
        idx++;
        if ((line[idx - 2] == '\r') && (line[idx - 1] == '\n')) {
            break;
        }
    }
    http_ctx->sysdep->service_sysdep_mutex_unlock(http_ctx->all_mutex.recv_mutex);
    return _service_htoi(line);
}

static int32_t _service_http_recv_body_chunked(service_http_ctx_t *http_ctx)
{
    int32_t res = RET_SUCCESS;
    int32_t body_len = 0;
    char *buffer = NULL;
    int32_t flag = 0;

    body_len = _service_get_chunked_count(http_ctx);
    log_debug("Http get len %d\r\n",body_len);
    buffer= http_ctx->sysdep->service_sysdep_malloc(body_len);
    if (buffer == NULL) {
        return RET_SYS_DEPEND_MALLOC_FAILED;
    }
    memset(buffer, 0, body_len);
    http_ctx->sysdep->service_sysdep_mutex_lock(http_ctx->all_mutex.recv_mutex);
    res = _service_http_recv(http_ctx, (uint8_t *)(buffer), body_len, http_ctx->recv_timeout_ms);
    http_ctx->sysdep->service_sysdep_mutex_unlock(http_ctx->all_mutex.recv_mutex);

    if (body_len > 0) {
        tiot_http_recv_t packet;

        if (http_ctx->service_recv_ctx != NULL) {
            http_ctx->session.body_read_len += body_len;
            memset(&packet, 0, sizeof(tiot_http_recv_t));
            packet.type = TIOT_HTTPRECV_BODY;
            packet.data.body.buffer = (uint8_t *)buffer;
            packet.data.body.len = body_len;

            http_ctx->recv_ctx(http_ctx, &packet, http_ctx->service_userdata);
        }
    }
    http_ctx->sysdep->service_sysdep_free(buffer);

    return RET_HTTP_READ_BODY_FINISHED_CHUNCED;
}

static int32_t _service_http_recv_body(service_http_ctx_t *http_ctx, uint32_t body_total_len)
{
    int32_t res = RET_SUCCESS;
    char *buffer = NULL;
    uint32_t remaining_len = 0, buffer_len = 0;

    if (http_ctx->session.body_total_len == 0 && body_total_len == 0) {
        return RET_HTTP_READ_BODY_EMPTY;
    }

    if (body_total_len != 0) {
        http_ctx->session.body_total_len = body_total_len;
        remaining_len = body_total_len;
    } else {
        remaining_len = http_ctx->session.body_total_len - http_ctx->session.body_read_len;
    }

    if (remaining_len == 0) {
        return RET_HTTP_READ_BODY_FINISHED;
    }

    buffer_len = (remaining_len < http_ctx->body_buffer_max_len) ? (remaining_len) : (http_ctx->body_buffer_max_len);

    buffer = http_ctx->sysdep->service_sysdep_malloc(buffer_len);
    if (buffer == NULL) {
        return RET_SYS_DEPEND_MALLOC_FAILED;
    }
    memset(buffer, 0, buffer_len);

    http_ctx->sysdep->service_sysdep_mutex_lock(http_ctx->all_mutex.recv_mutex);
    res = _service_http_recv(http_ctx, (uint8_t *)buffer, buffer_len, http_ctx->recv_timeout_ms);

    http_ctx->sysdep->service_sysdep_mutex_unlock(http_ctx->all_mutex.recv_mutex);
    if (res > 0) {
        tiot_http_recv_t packet;

        if (http_ctx->service_recv_ctx != NULL) {
            http_ctx->session.body_read_len += res;
            memset(&packet, 0, sizeof(tiot_http_recv_t));
            packet.type = TIOT_HTTPRECV_BODY;
            packet.data.body.buffer = (uint8_t *)buffer;
            packet.data.body.len = res;

            http_ctx->service_recv_ctx(http_ctx, &packet, http_ctx->service_userdata);
        }
    }
    http_ctx->sysdep->service_sysdep_free(buffer);

    return res;
}

int32_t service_http_recv(void *ctx)
{
    int32_t res = RET_SUCCESS;
    uint32_t body_total_len = 0;
    service_http_ctx_t *http_ctx = (service_http_ctx_t *)ctx;

    if (http_ctx == NULL) {
        return RET_USER_INPUT_NULL_POINTER;
    }

    if (http_ctx->network_ctx == NULL) {
        return RET_SYS_DEPEND_NWK_CLOSED;
    }

    if (http_ctx->service_exec_enabled == 0) {
        return RET_USER_INPUT_EXEC_DISABLED;
    }

    _service_http_exec_inc(http_ctx);

    if (http_ctx->session.sm == SERVICE_HTTP_SM_READ_HEADER) {
        http_ctx->sysdep->service_sysdep_mutex_lock(http_ctx->all_mutex.recv_mutex);
        res = _service_http_recv_header(http_ctx, &body_total_len);
        http_ctx->sysdep->service_sysdep_mutex_unlock(http_ctx->all_mutex.recv_mutex);
        if (res < RET_SUCCESS) {
            _service_http_exec_dec(http_ctx);
            return res;
        }
    }
    http_ctx->session.sm = SERVICE_HTTP_SM_READ_BODY;

    res = _service_http_recv_body(http_ctx, body_total_len);
    if (res == RET_HTTP_READ_BODY_FINISHED) {
        memset(&http_ctx->session, 0, sizeof(service_http_session_t));
    }else if(res == RET_HTTP_READ_BODY_EMPTY){
        memset(&http_ctx->session, 0, sizeof(service_http_session_t));
        res = _service_http_recv_body_chunked(http_ctx);
    }

    _service_http_exec_dec(http_ctx);

    return res;
}

int32_t service_http_deinit(void **p_ctx)
{
    uint32_t deinit_timeout_ms = 0;
    service_http_ctx_t *http_ctx = NULL;

    if (p_ctx == NULL || *p_ctx == NULL) {
        return RET_USER_INPUT_NULL_POINTER;
    }

    http_ctx = *(service_http_ctx_t **)p_ctx;

    if (http_ctx->service_exec_enabled == 0) {
        return RET_USER_INPUT_EXEC_DISABLED;
    }

    http_ctx->exec_enabled = 0;
    deinit_timeout_ms = http_ctx->deinit_timeout_ms;
    do {
        if (http_ctx->exec_count == 0) {
            break;
        }
        http_ctx->sysdep->service_sysdep_sleep(SERVICE_HTTP_DEINIT_INTERVAL_MS);
    } while ((deinit_timeout_ms > SERVICE_HTTP_DEINIT_INTERVAL_MS) && (deinit_timeout_ms - SERVICE_HTTP_DEINIT_INTERVAL_MS > 0));

    if (http_ctx->exec_count != 0) {
        return RET_HTTP_DEINIT_TIMEOUT;
    }

    if (http_ctx->network_ctx != NULL) {
        http_ctx->sysdep->service_sysdep_network_deinit(&http_ctx->network_ctx);
    }

    if (http_ctx->host != NULL) {
        http_ctx->sysdep->service_sysdep_free(http_ctx->host);
    }
    if (http_ctx->cred != NULL) {
        http_ctx->sysdep->service_sysdep_free(http_ctx->cred);
    }

    service_sysdep_mutex_deinit(http_ctx);
    
    http_ctx->sysdep->service_sysdep_free(http_ctx);

    *p_ctx = NULL;

    return RET_SUCCESS;
}

